/*********************************************************************
 * Copyright (C) 2002 Andrew Khan
 * <p>
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * <p>
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * <p>
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 ***************************************************************************/

package jxl.biff;

import jxl.common.Logger;
import jxl.read.biff.Record;

/**
 * Extension of the standard RecordData which is used to support those
 * records which, once read, may also be written
 */
public abstract class WritableRecordData extends RecordData
        implements ByteData {
    /**
     * The logger
     */
    private static Logger logger = Logger.getLogger(WritableRecordData.class);
    /**
     * The maximum length allowed by Excel for any record length
     */
    protected static final int maxRecordLength = 8228;

    /**
     * Constructor used by the writable records
     *
     * @param t the biff type of this record
     */
    protected WritableRecordData(Type t) {
        super(t);
    }

    /**
     * Constructor used when reading a record
     *
     * @param t the raw data read from the biff file
     */
    protected WritableRecordData(Record t) {
        super(t);
    }

    /**
     * Used when writing out records.  This portion of the method handles the
     * biff code and the length of the record and appends on the data retrieved
     * from the subclasses
     *
     * @return the full record data to be written out to the compound file
     */
    public final byte[] getBytes() {
        byte[] data = getData();

        int dataLength = data.length;

        // Don't the call the automatic continuation code for now
        //    Assert.verify(dataLength <= maxRecordLength - 4);
        // If the bytes length is greater than the max record length
        // then split out the data set into continue records
        if (data.length > maxRecordLength - 4) {
            dataLength = maxRecordLength - 4;
            data = handleContinueRecords(data);
        }

        byte[] bytes = new byte[data.length + 4];

        System.arraycopy(data, 0, bytes, 4, data.length);

        IntegerHelper.getTwoBytes(getCode(), bytes, 0);
        IntegerHelper.getTwoBytes(dataLength, bytes, 2);

        return bytes;
    }

    /**
     * The number of bytes for this record exceeds the maximum record
     * length, so a continue is required
     * @param data the raw data
     * @return the continued data
     */
    private byte[] handleContinueRecords(byte[] data) {
        // Deduce the number of continue records
        int continuedData = data.length - (maxRecordLength - 4);
        int numContinueRecords = continuedData / (maxRecordLength - 4) + 1;

        // Create the new byte array, allowing for the continue records
        // code and length
        byte[] newdata = new byte[data.length + numContinueRecords * 4];

        // Copy the bona fide record data into the beginning of the super
        // record
        System.arraycopy(data, 0, newdata, 0, maxRecordLength - 4);
        int oldarraypos = maxRecordLength - 4;
        int newarraypos = maxRecordLength - 4;

        // Now handle all the continue records
        for (int i = 0; i < numContinueRecords; i++) {
            // The number of bytes to add into the new array
            int length = Math.min(data.length - oldarraypos, maxRecordLength - 4);

            // Add in the continue record code
            IntegerHelper.getTwoBytes(Type.CONTINUE.value, newdata, newarraypos);
            IntegerHelper.getTwoBytes(length, newdata, newarraypos + 2);

            // Copy in as much of the new data as possible
            System.arraycopy(data, oldarraypos, newdata, newarraypos + 4, length);

            // Update the position counters
            oldarraypos += length;
            newarraypos += length + 4;
        }

        return newdata;
    }

    /**
     * Abstract method called by the getBytes method.  Subclasses implement
     * this method to incorporate their specific binary data - excluding the
     * biff code and record length, which is handled by this class
     *
     * @return subclass specific biff data
     */
    protected abstract byte[] getData();
}
